# Flow-IPC: Structured Transport
# Copyright (c) 2023 Akamai Technologies, Inc.; and other contributors.
# Each commit is copyright by its respective author or author's employer.
#
# Licensed under the MIT License:
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

@0xe04fe86779eddbc8; # Manually generated by `capnp id` and saved here for all time.

using Cxx = import "/capnp/c++.capnp";

# The following section is, informally, this schema file's *header*.
# Recommendation: copy-paste relevant pieces into other schema files + follow similar conventions in this
# and other library+API combos.
#
# Header BEGIN ---

# Quick namespace reference:
# - ipc::transport::struc: Globally used for all IPC transport structured layer-related matters in
#   Flow-IPC library+API.
#   - schema: Indicates the symbols therein are auto-generated by capnp from a schema such as in this file.
#     - detail: Not to be used by external user of this library+API but merely internally.
$Cxx.namespace("ipc::transport::struc::schema::detail");

# --- structured_msg.capnp ---
# See class Msg_mdt_out.  This is the information internally encoded by us for each out-message sent by
# struc::Channel::send_core().
#   - For a user message, it describes that user message; message ID is non-zero.
#   - For an internal user message, it describes and *contains* the internal message; message ID is zero.

# --- END Header.

# Types used by schema below.

using Common = import "/ipc/transport/struc/schema/common.capnp";

using Size = Common.Size;
using SessionToken = Common.Uuid; # Session tokens used for safety in all structured messages.
using ProtoVer = Int16; # Isomorphic to `Protocol_version::proto_ver_t`.  See discussion in its doc header.

# Main schema.

struct StructuredMessage
{
  using MessageId = UInt64;
  # Isomorphic to `msg_id_t`.  See discussion in its doc header.

  struct AuthHeader
  {
    # So far it's just sessionToken but feels reasonable to segregate the safety stuff in a struct from the start.
    # The protocol then can compatibly grow (per capnp docs) without adding related fields away from sessionToken
    # but rather by adding them inside AuthHeader.
    # (Though they could be placed near sessionToken -- but with a non-contiguous field ID, which might be confusing.)

    sessionToken @0 :SessionToken;
    # There is one channel called session master channel; it has a log-in phase
    # (See session_master_channel.capnp LogInReq and LogInRsp for how ipc::session does it.
    # Using that nonmenclature then we have the following.)
    #   - LogInReq shall have sessionToken=nil.
    #   - LogInRsp shall have sessionToken=<uniquely generated one by session-server>.
    # For these messages, and these messages only, the check shall be against:
    #   - nil,
    #   - non-nil
    # respectively.
    #
    # For all subsequent messages in that channel *and* all subsequent channels in that session,
    # sessionToken=X, where X is the one in LogInRsp.
    # For all those subsequent messages the check shall be against:
    #   - X.
    # If the sessionToken fails any of the above checks, the channel shall close immediately.  It is a
    # safety measure.
  }

  authHeader @0 :AuthHeader;

  id @1 :MessageId = 0;
  # Unique per channel per sending process (of which there are 2); 1, 2, ....
  # Necessary for request/response association; possible as sequence number; and helpful for logging/debugging
  # otherwise.
  # 0 is a sentinel value (and the default, explicitly specified here for clarity, even though it would be 0 anyway).
  # It is actively used only for internal info messages not bearing a `body` (see below).
  # Otherwise it must be one of 1, 2, ....

  struct OriginatingMessage
  {
    # A message is either solicited by another message (then originatingMessageOrNull is non-null); or not
    # (then it is null).
    #
    # A solicited message is a *response* to another message.  Otherwise it is an *unsolicited* message.
    # These are formal concepts in the schema; simply, a response has an originatingRequestId, while
    # an unsolicited message does not.  Hence the following field ends in `ElseNull` to remind one:
    # it may well be null (uninitialized), meaning it's not a reponse.
    #
    # Informally, any message (unsolicited or response) is either
    #   - a *notification* -- meaning it does not itself expect a response -- or:
    #   - a *request* -- meaning it does expect a response.
    # However that is not a part of this formal schema but rather a user decision at the time they use the
    # ipc::transport C++ API, only to facilitate routing the resulting response (if applicable) to the request's
    # given response callback.  Again: the schema doesn't care about it or enforce it.  It only cares about
    # the added datum, originatingRequestId, when a message is in fact a response, which the user must indicate
    # when setting up the StructuredMessage.
    #
    # Moreover, there is no enforcement at the schema level as to what is encoded inside a particular MessageBody
    # (usually in terms of which union value is chosen inside it by the user) relative to whether it's
    # unsolicited or a response.  That is, a particular MessageBody sub-message may be intended to always be
    # a response, and hence be accompanied by an originatingRequestId, but it is up to the user to
    # engage the proper ipc::transport API to ensure the present union is set to `response` when storing
    # that MessageBody sub-message.  The details are outside the scope of this discussion, but we wanted to
    # alert the reader to these issues, for background/context.

    id @0 :MessageId;
    # See above.  Reminder: As of this writing this must not be 0 (sentinel): we do not respond to internal-messages.
  }

  originatingMessageOrNull @2 :OriginatingMessage;
  # See above struct comment.

  struct InternalMessageBody
  {
    # Internal info message, as opposed to a user-payload-message body.

    struct UnexpectedResponse
    {
      # Indicates the sender has received a user-payload-message-bearing response (i.e., one with non-null
      # originatingMessageOrNull and a `body`) that the sender's code had not, at that time, been expecting.
      # E.g.:
      #   - Receiver sent message responding to a non-request.
      #   - Receiver sent 2nd/3rd/... response to a one-off request.
      #   - Receiver sent response to an open-ended request but after sender unregistered the expectation.
      #     Typically, receiver had earlier indicated somehow that this is the final response to a given request --
      #     causing sender to unregister expecting further responses -- and then sent yet another response anyway.
      # Note: .originatingMessage (our peer) shall indicate the offending receiver-sent response message.

      originatingMessageMetadataText @0 :Text;
      # Brief string summarizing that message's context (perhaps for easy logging).  E.g., it could be
      # a pretty-printed deserialization of it.
    } # struct UnexpectedResponse

    # Union must have 2+ members.  For now comment this out; note uncommenting it when there are 2+ is
    # bit-compatible with this; so this is safe.
    # union
    # {

    unexpectedResponse @0 :UnexpectedResponse;

    # }
  } # struct InternalMessageBody

  union
  {
    numBodySerializationSegments @3 :Size;
    # If this metadata describing user-payload, this is the # of segments following this that serialize that payload.
    # Only 1+ is legal.

    internalMessageBody @4 :InternalMessageBody;
    # Otherwise this is the internal message itself (and there is no user message).
  }
} # struct StructuredMessage

struct ProtocolNegotiation
{
  # struc::Channel sends and expects, ahead of any other message, this protocol-negotiation structure.
  # We expect this to stay stable, even as other protocols (such as StructuredMessage above) gain changes/new
  # versions over time.  (If it does not stay stable, at least any changes to it must be backwards-compatible
  # by following capnp rules on making safe changes -- the web site has a little section on how to do so;
  # e.g., it is safe to add more fields, with higher ordinals.  Hopefully though it can just stay stable.)

  maxProtoVer @0 :ProtoVer;
  # ipc_transport_structured-module sender-preferred protocol version (1 or higher).
  # See struc::sync_io::Channel doc header "Protocol negotiation" section.

  maxProtoVerAux @1 :ProtoVer;
  # Sender-preferred protocol version of any additional protocol chosen via Struct_builder_config template parameter
  # to struc::sync_io::Channel; set to 1 if there is no additional (versus maxProtoVer) protocol in play
  # (meaning if Struct_builder_config = Heap_fixed_builder::Config).
  # See struc::sync_io::Channel doc header "Protocol negotiation" section.
}
